package com.morpheus.redis.redisson.api.guid;


import java.net.InetAddress;
import java.net.NetworkInterface;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.collections4.CollectionUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import com.alibaba.fastjson.JSON;

/**
 * @author jierui
 * @package io.zkjy.redis.redisson.api.guid
 * @date 2021/8/12-9:06
 * @description:
 */
public class GuidSnowRedisUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(GuidSnowRedisUtil.class);
    private static final Long WORKID_POOL_SIZE = 1024L;
    private static final String WORKID_BASE_KEY = "REDIS_SNOW_WORKID:";
    private static final String WORKID_AVAILABLE_KEY = "REDIS_SNOW_WORKID_AVAILABLE";
    private static final String WORKID_UNAVAILABLE_KEY = "REDIS_SNOW_WORKID_UNAVAILABLE";
    private static final String WORKID_LOCK="REDIS_SNOW_WORKID_LOCK";
    private static final String WORKID_GETNEW_LOCK="REDIS_SNOW_WORKID_GETNEW_LOCK";

    private volatile static boolean initialized = false;

    private static RedisTemplate<String, Object> redisTemplate;

    private static RedissonClient redissonClient;

    private static String workKey;

    /**
     * 初始化方法
     * @param redissonClientArgs
     * @param redisTemplateArgs
     * @param appMarkArgs 应用标记
     */
    public synchronized static void initGuidSnowRedisUtil(RedissonClient redissonClientArgs,RedisTemplate<String, Object> redisTemplateArgs, String appMarkArgs) {
        LOGGER.error("GuidSnowRedisUtil.init() appMark={}",JSON.toJSONString(appMarkArgs));


        if (initialized) {
            return;
        }
        redisTemplate = redisTemplateArgs;
        redissonClient=redissonClientArgs;
        workKey = WORKID_BASE_KEY + appMarkArgs;
        initialized = true;

        Long workId = getAvailabWorkId();
        if (workId != null) {
            createSnowWork(workId);
        }else {
            throw new RuntimeException(appMarkArgs+"Redis雪花Id生成器获取组件异常");
        }

    }

    /**
     * 创建代码生成类
     */
    private static void createSnowWork(Long id) {
        SnowflakeIdFactory snowflakeIdFactory = SnowflakeIdFactory.initSnowFlakeWorker(id);
        LOGGER.debug("GuidSnowRedisUtil.createSnowWork() snowflakeIdFactory={}", snowflakeIdFactory);
    }


    /**
     * 过滤可用workId
     *
     * @param
     * @return
     */
    public static Long getAvailabWorkId() {

        Long workId = null;
        workId = (Long) redisTemplate.opsForValue().get(workKey);
        if (workId != null) {
            return workId;
        } else {
            workId = getWorkId(null);
            redisTemplate.opsForValue().set(workKey, workId);
            return workId;
        }
    }

    /**
     * 获取workId缓存可用列表
     *
     * @return
     */
    public static LinkedHashMap<Long, Long> getAvailabHash() {
        return  (LinkedHashMap)redisTemplate.boundHashOps(WORKID_AVAILABLE_KEY).entries();
    }

    /**
     * 获取workId缓存不可用列表
     *
     * @return
     */
    public static LinkedHashMap<Long, Long> getUnAvailabHash() {
        return  (LinkedHashMap) redisTemplate.boundHashOps(WORKID_UNAVAILABLE_KEY).entries();
    }


    /**
     * 旧id存储到不可用集合，保存时间，新id获取返回 旧Id为空，返回初始化ID
     *
     * @param oldWorkId
     * @return
     */
    public static Long getWorkId(Long oldWorkId) {
        RLock lock = redissonClient.getLock(WORKID_LOCK);
        Long workId = null;
        /**
         * 可用列表
         * 前 iD 后时间戳
         */
        Set<Long> availableSet = new HashSet<>();
        LinkedHashMap<Long, Long> availabHash = getAvailabHash();

        availabHash.entrySet().stream().forEach(e -> {
            availableSet.add(Long.parseLong(String.valueOf(e.getKey())));
        });

        if(WORKID_POOL_SIZE.compareTo((long) availabHash.size())==0){
            throw new RuntimeException("workId耗尽，程序错误，检查当前操作系统时间");
        }
        /**
         * 不可用列表
         * 前 iD 后时间戳
         */
        Set<Long> unavailableSet = new HashSet<>();
        LinkedHashMap<Long, Long> unAvailabHash = getUnAvailabHash();
        unAvailabHash.entrySet().stream().forEach(e -> {
            unavailableSet.add(Long.parseLong(String.valueOf(e.getKey())));
        });


        try {
            if(lock.tryLock(500, 30000, TimeUnit.MILLISECONDS)){
                LOGGER.error("GuidSnowRedisUtil.getWorkId() 分布式锁--程序加锁---workId获取中");
                Set<Long> workPoolSet = new HashSet<>();                //初始化id池
                for (int i = 0; i < WORKID_POOL_SIZE; i++) {
                    workPoolSet.add(new Long(i));
                }
                //初始化后 清理可用及不可用列表 小于总数，删除 可用+不可用，返回未分配
                if (availableSet.size() + unavailableSet.size() < WORKID_POOL_SIZE) {
                    //不可以用+可用id池之和等于最大id池大小时，只从初始化池中删除可用 反之 删除可用+不可用，选出未分配
                    workPoolSet.removeAll(availableSet);
                    workPoolSet.removeAll(unavailableSet);
                    //获取可用workId 1.缓存可用列表 2.缓存项目配置
                    if (CollectionUtils.isNotEmpty(workPoolSet)) {
                        Object[] objects = workPoolSet.toArray();
                        workId = (Long) objects[0];
                        redisTemplate.opsForValue().set(workKey, workId);
                        redisTemplate.boundHashOps(WORKID_AVAILABLE_KEY).put(workId.toString(), Calendar.getInstance().getTimeInMillis());
                    }
                } else {
                    Optional<Map.Entry<Long, Long>> longTimeMap = unAvailabHash.entrySet()
                            .stream().min(Map.Entry.comparingByValue());
                    Map.Entry<Long, Long> longDateEntry = longTimeMap.get();
                    if (longDateEntry.getValue() < Calendar.getInstance().getTimeInMillis()) {
                        workId = Long.parseLong(String.valueOf(longDateEntry.getKey()));

                        //去除一个时间戳最久远的当新id,从不可用列表中剔除
                        redisTemplate.boundHashOps(WORKID_AVAILABLE_KEY).delete(workId.toString());
                        redisTemplate.opsForValue().set(workKey, workId);
                        redisTemplate.boundHashOps(WORKID_AVAILABLE_KEY).put(workId.toString(), Calendar.getInstance().getTimeInMillis());
                    }
                }
            }
        } catch (Throwable th) {
            LOGGER.error("GuidSnowRedisUtil.getWorkId() 分布式锁使用异常，{}",th);


        }finally {

            if(oldWorkId!=null){
                redisTemplate.boundHashOps(WORKID_AVAILABLE_KEY).delete(oldWorkId.toString());
                redisTemplate.boundHashOps(WORKID_UNAVAILABLE_KEY).put(oldWorkId.toString(), Calendar.getInstance().getTimeInMillis());
            }
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 E HH点mm分ss秒");
            LOGGER.error("GuidSnowRedisUtil.getWorkId() 数据置换，可用不可用互换id存储====雪花id生成器===OLDID:{},TIME:{}",oldWorkId, sdf.format(Calendar.getInstance().getTimeInMillis()));
            lock.unlock();
            LOGGER.error("GuidSnowRedisUtil.getWorkId() 分布式锁--程序解锁");
        }

        return workId;
    }


    /**
     * 获取网卡ip
     *
     * @param ipStr 网段
     * @retur
     */
    public static String getLocalHost(String ipStr) {
        String hostAddress = "";
        try {
            Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
            while (netInterfaces.hasMoreElements()) {
                NetworkInterface ni = netInterfaces.nextElement();
                System.out.println("DisplayName:" + ni.getDisplayName());
                System.out.println("Name:" + ni.getName());
                Enumeration<InetAddress> ips = ni.getInetAddresses();
                while (ips.hasMoreElements()) {
                    hostAddress = ips.nextElement().getHostAddress();
                    if (hostAddress.contains(ipStr)) {
                        return hostAddress;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return hostAddress;
    }


}
